/**
 *  @file KBConnection.java
 *  @author Mamontov
 *
 *  This file contains KBConnection class definition
 */
package db21;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.racersystems.jracer.RacerClient;
import com.racersystems.jracer.RacerClientException;
import com.racersystems.jracer.RacerList;
import com.racersystems.jracer.RacerResult;
import com.racersystems.jracer.RacerSymbol;
import db21.racerpro.Queries;
import db21.racerpro.RacerProResult;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import sun.misc.Regexp;

/**
 * @class  KBConnection
 * @author Mamontov
 * A KBConnection contains everything, that works with connection and hooks for
 * syncing RacerPro and Jena
 *
 */
public class KBConnection {
    /**
     *  Whether connection is opened
     */
    boolean  m_opened;
    /**
     * Prefix for various purposes
     */
    String   m_prefix;
    /**
     * Port for Racer Connection
     */
    int       m_port;
    /**
     * IP for Racer Connection
     */
    String    m_ip;
    /**
     * Whether KB Path is absolute
     */
    boolean   m_is_absolute_path;
    /**
     *  A name of source KB
     */
    String  m_source;
    /**
     * A jena model data
     */
    OntModel m_jenamodel;
    /**
     *   A model for Jena
     */
    OntModel m_jenafactorymodel;
    /**
     *   A racer client 
     */
    RacerClient m_racerclient;
    
    
    /*! Create new connection with specified IP, port, 
     *  whether it is an absolute path, with source and prefix
     */
    public KBConnection(String ip, 
                        int port, 
                        boolean is_absolute_path, 
                        String source,
                        String prefix)
    {
        m_ip = ip;
        m_port = port;
        m_is_absolute_path = is_absolute_path;
        m_source = source;
        m_prefix = prefix;
        m_racerclient = new RacerClient(m_ip, m_port);
        m_jenafactorymodel = ModelFactory.createOntologyModel();
        m_opened = false;
    }
    
    public boolean open() {
        boolean result = true;
        
        m_jenamodel = (OntModel)m_jenafactorymodel.read(
                            this.getWritableKnowledgeBasePath()
                      );
        if (m_jenamodel != null) {
           try {
                m_racerclient.openConnection();
                String path = this.getRacerProKnowledgeBasePath();
                String query = this.buildQuery(Queries.read(), path);
                m_racerclient.sendRaw(query); 
                m_opened = true;
           }
           catch (Exception e) {
                e.printStackTrace();
                result = false;
           } 
        } else {
            result = false;
        }
        return result;
    }
    
    public void syncWithRacerPro()  {
        FileOutputStream tempstream = null;
        try {
            // Write temporary file
            String wpath = this.getWritableTempKnowledgeBasePath();
            tempstream = new FileOutputStream(wpath);
            m_jenamodel.write(tempstream);
            tempstream.close();
            
            String path = this.getRacerProTempKnowledgeBasePath();
            m_racerclient.fullReset();
            String query = this.buildQuery(Queries.read(), path);
            m_racerclient.sendRaw(query); 
        } catch (Exception ex) {
           
        } 
    } 
    
    private void unsafeSave()  {
        FileOutputStream stream;
        try {
            stream = new FileOutputStream(this.getWritableKnowledgeBasePath());
            m_jenamodel.write(stream);
            stream.close();
        } catch (Exception ex) {
            Logger.getLogger(KBConnection.class.getName()).log(Level.SEVERE, null, ex);
        }       
    }
    
    public void save() {
        if (m_jenamodel != null) {
            unsafeSave();
        }
    }
    
    public void close()  {
        FileOutputStream stream = null;
        try {
            if (m_opened) {
                m_racerclient.closeConnection();
                m_opened = false;
            }
            save();
        } 
        catch (Exception ex) {
            Logger.getLogger(KBConnection.class.getName()).log(Level.SEVERE, null, ex);
        } 
    }
    
    private OntClass  classByName(String className)  {
        ExtendedIterator<OntClass> listClasses = m_jenamodel.listClasses();
        while(listClasses.hasNext()) {
            OntClass c = listClasses.next();
            if (c.getLocalName().equals(className)) {
                return c;
            }
        }
        return null;
    }  
    
    public String prefix() {
        return m_prefix;
    }
    
    public void removeIndividual(String name) {
        Individual i = this.m_jenamodel.getIndividual(Queries.escapeValue(m_prefix, name));
        i.remove();
    }
    
    public Individual  createIndividual(String className, String myName) {
        OntClass c =  this.classByName(className);
        String name = Queries.escapeValue(m_prefix, myName);
        if (m_jenamodel.getIndividual(name) != null) {
            return null;
        }
        return c.createIndividual(Queries.escapeValue(m_prefix, myName));
    }
    
    
    
    public String evaluateSparQL(String expr) {
        String s = this.buildQuery(Queries.sparql(), this.m_prefix, expr);
        String racerAnswerQuery = "";
        try {
            racerAnswerQuery = m_racerclient.sendRaw(s);
        } catch (RacerClientException ex) {
            Logger.getLogger(KBConnection.class.getName()).log(Level.SEVERE, null, ex);
        }
        return racerAnswerQuery;
    }
    
    
    public RacerProResult asResult(String result) {
        RacerList<RacerList<RacerList<RacerSymbol>>>  l = 
       (RacerList<RacerList<RacerList<RacerSymbol>>>)
        m_racerclient.parseRacerAnswer(result);
        RacerProResult r = new RacerProResult(l, m_prefix);
        return r;
    }
    
    public String buildQuery(String template, String[] tmpl) {
        String result = template;
        for(int i = 0; i < tmpl.length; i++) {
            Integer t = i;
            result = result.replaceAll(Pattern.quote("$" + t.toString()), tmpl[i]);
        }
        return result;
    }
    
    public String buildQuery(String template, String a1) {
        String a[] = {a1};
        return buildQuery(template, a);
    } 
    public String buildQuery(String template, String a1, String a2) {
        String a[] = {a1, a2};
        return buildQuery(template, a);
    } 
    public String buildQuery(String template, String a1, String a2, String a3) {
        String a[] = {a1, a2, a3};
        return buildQuery(template, a);
    } 
    public String buildQuery(String template, String a1, String a2, String a3,
                             String a4) {
        String a[] = {a1, a2, a3, a4};
        return buildQuery(template, a);
    } 
    
    
    
    
    public String getAbsolutePathToKnowledgeBasePath(String name) {
        String path = this.getClass().getClassLoader().getResource("").getPath();
        return path.concat(name);
    }
    
    public String getWritableKnowledgeBasePath() {
        if (m_is_absolute_path) {
            if (m_source.length() == 0) {
                return  "/";
            }
            if (m_source.startsWith("/") == false) {
                return "/".concat(m_source);
            }
        }
        return this.getAbsolutePathToKnowledgeBasePath(m_source);
    }
    
    public String getRacerProKnowledgeBasePath() {
        return this.getWritableKnowledgeBasePath().substring(1);
    }
    
    public String getWritableTempKnowledgeBasePath() {
        return this.getAbsolutePathToKnowledgeBasePath("temp.owl");
    }
    public String getRacerProTempKnowledgeBasePath() {
        return this.getWritableTempKnowledgeBasePath().substring(1);
    }
    
    /**
     * Well, RacerPro does not support + and UNION in SparQL queries in 2.0 
     * preview version. So, here comes Jena
     * And nobody cares about it.
     * @param queryString string query
     * @param name  of variable for sparql
     * @return result
     */
    public ArrayList<String> evaluateSparQLInJena(String tQueryString, String name) {
        ArrayList<String> result = new ArrayList<String>();
        String queryString = "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> "
                           .concat("PREFIX p: <" + m_prefix + "> ")
                           .concat(tQueryString)
                           ;
        Query query = QueryFactory.create(queryString);
        QueryExecution qexec = QueryExecutionFactory.create(query, m_jenamodel);
        Iterator<QuerySolution> results = qexec.execSelect();
        for ( ; results.hasNext() ; )
        {
            QuerySolution soln = results.next() ;
            String escaped = soln.getResource(name).getLocalName();
            String val = Queries.unescapeLocalName(escaped);
            result.add(val);
        }
        return result;
    }
    
    public Property getProperty(String name) {
        return m_jenamodel.getProperty(m_prefix + name);
    }
    
    public String getClassOfIndividual(String name) {
        return this.getIndividual(name).getOntClass().getLocalName();
    }
    
    public Individual getIndividual(String name) {
        String escaped = Queries.escapeValue(m_prefix, name);
        return m_jenamodel.getIndividual(escaped);
    }
}
